package lfp.engine.similarity;

import org.apache.log4j.Logger;

import java.util.*;

import lfp.engine.utils.NormalDistribution;

/**
 * Created by IntelliJ IDEA.
 * User: zhangsiyuan
 * Date: 2007-8-27
 * Time: 16:39:29
 */
public class SimilarityQuery {
    private static final Logger log = Logger.getLogger(SimilarityQuery.class);
    private List<STimeSeries> historySpace;

    private List<STimeSeries> querySpace;
    private STimeSeries destTimeSeries;

    private Map<Integer, STimeSeries> similarMap;
    private int maxSimilarMapSize;

    private double[] weightSeries;

    private double[] distances;
    private double[] para_a;
    private double[] para_b;

    private int forecastLen;

    public SimilarityQuery(List<STimeSeries> historySpace, STimeSeries destTimeSeries) {
        this.historySpace = historySpace;
        this.destTimeSeries = destTimeSeries;
        initialQuerySpace();
        similarMap = new HashMap<Integer, STimeSeries>();
        weightSeries = new double[destTimeSeries.getLen()];
        for (int i = 0; i < weightSeries.length; i++)
            weightSeries[i] = 1;
        distances = new double[historySpace.size()];
        para_a = new double[historySpace.size()];
        para_b = new double[historySpace.size()];
    }

    public SimilarityQuery(List<STimeSeries> historySpace, STimeSeries destTimeSeries,
                           double[] weightSeries, int maxSimilarMapSize) {
        this.historySpace = historySpace;
        this.destTimeSeries = destTimeSeries;
        this.weightSeries = weightSeries;
        this.maxSimilarMapSize = maxSimilarMapSize;
        initialQuerySpace();
        similarMap = new HashMap<Integer, STimeSeries>();
        distances = new double[historySpace.size()];
        para_a = new double[historySpace.size()];
        para_b = new double[historySpace.size()];
    }

    private void initialQuerySpace() {
        if (historySpace == null || historySpace.size() == 0 || destTimeSeries == null) {
            log.info("No history data can be found!");
            return;
        }
        STimeSeries historySeries = historySpace.get(0);
        STimeSeries querySeries;
        forecastLen = historySeries.getLen() - destTimeSeries.getLen();
        querySpace = new ArrayList<STimeSeries>();
        for (STimeSeries aHistorySeries : historySpace) {
            querySeries = aHistorySeries.getSubTimeSeries(0, 1, destTimeSeries.getLen());
            querySpace.add(querySeries);
        }
    }

    public void calDistances() {
        int i = 0;
        double[] transPara;
        for (STimeSeries aTimeSeries : querySpace) {
            transPara = new double[2];
            distances[i] = destTimeSeries.calDWithLinearTrans(aTimeSeries, weightSeries, transPara);
            para_a[i] = transPara[0];
            para_b[i] = transPara[1];
            i++;
        }
    }

    public void query(double initialEpsilon) {
        double e1 = 0;
        double e2 = initialEpsilon;
        int similarMapSize = 0;
        while (similarMapSize < maxSimilarMapSize - 1) {
            for (int i = 0; i < historySpace.size(); i++) {
                if (distances[i] >= e1 && distances[i] < e2) {
                    similarMap.put(i, historySpace.get(i));
                    similarMapSize++;
                }
            }
            e1 = e2;
            e2 *= 2;
        }
    }

    public void query() {
        int similarMapSize = 0;
        int index = -1;
        Set<Integer> ignoreIndexSet = new HashSet<Integer>();
        while (similarMapSize < maxSimilarMapSize) {
            ignoreIndexSet.add(index);
            index = minValueAt(distances, ignoreIndexSet);
            similarMap.put(index, historySpace.get(index));
            similarMapSize++;
        }
    }

    public double[] forecast(STimeSeries aTimeSeries, double a, double b, double d) {
        double[] result = new double[forecastLen];
        double miu = 0;
        double sigma = d/destTimeSeries.getLen();
        NormalDistribution normal = new NormalDistribution(miu, sigma);
        int count = 0;
        for (int i = destTimeSeries.getLen(); i < destTimeSeries.getLen() + forecastLen; i++)
            result[count++] = a * aTimeSeries.getSeriesAt(i) + b + normal.getNormalRandom();
        return result;
    }

    public STimeSeries getExpandedDTS() {
        // 1. calDistances
        calDistances();
        // 2. query
        query();
        // 3. getUsefulMap
        STimeSeries expandedDTS = new STimeSeries(destTimeSeries.getLen() + forecastLen);
        expandedDTS.setSeries(destTimeSeries.getSeries(), 0, 0, destTimeSeries.getLen());
        Set<Integer> keySet = similarMap.keySet();
        double[] forecastTempValue;
        double[] forecastSeries = new double[forecastLen];
        double totalDistance = 0;
        for (Integer i : keySet) {
            forecastTempValue = forecast(similarMap.get(i), para_a[i], para_b[i], distances[i]);
            totalDistance += distances[i];
            for (int j = 0; j < forecastLen; j++)
                forecastSeries[j] += forecastTempValue[j] * distances[i];
        }
        for (int j = 0; j < forecastLen; j++)
            forecastSeries[j] /= totalDistance;
        expandedDTS.setSeries(forecastSeries, 0, destTimeSeries.getLen(), forecastLen);
        return expandedDTS;
    }

    private int minValueAt(double[] values, Set<Integer> ignoreIndexSet) {
        int index = -1;
        if (values == null || values.length == 0) return index;
        double min = Math.pow(10, 8);
        for (int i = 0; i < values.length; i++) {
            if (ignoreIndexSet.contains(i)) continue;
            if (min > values[i]) {
                min = values[i];
                index = i;
            }
        }
        return index;
    }

    // ========================= app ============================
}
